﻿using Microsoft.Extensions.Logging;
using Nantianmen.Core.Stores;
using Nantianmen.Domain.Models;
using Nantianmen.Domain.Repositories;
using Nantianmen.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Nantianmen.Core.Services
{
    public class ClusterService : ServiceBase, IClusterService
    {
        protected virtual CancellationToken CancellationToken { get; } = CancellationToken.None;
        protected virtual ILogger Logger { get; set; }
        protected virtual IClusterStore Store { get; set; }
        public NantianmenErrorDescriber ErrorDescriber { get; set; }

        public ClusterService(
            ILogger<ClusterService> logger,
            IClusterStore store,
            NantianmenErrorDescriber errorDescriber)
        {
            Logger = logger;
            Store = store;
            ErrorDescriber = errorDescriber;
        }

        public async Task<NantianmenResult> CreateClusterAsync(Cluster cluster)
        {
            ThrowIfDisposed();

            var errors = new List<NantianmenError>();

            var duplicated = await Store.CheckDuplicateClusterIdAsync(cluster.Id, CancellationToken).ConfigureAwait(false);

            if (duplicated)
            {
                errors.Add(NantianmenErrorDescriber.DuplicateClusterId(cluster.Id));
                return NantianmenResult.Failed(errors);
            }

            var result = await ValidateClusterAsync(cluster);
            if (result.Succeeded == false)
            {
                return result;
            }

            return await Store.CreateAsync(cluster, CancellationToken).ConfigureAwait(false);
        }

        public async Task<NantianmenResult> UpdateClusterAsync(Cluster cluster)
        {
            ThrowIfDisposed();

            var result = await ValidateClusterAsync(cluster).ConfigureAwait(false);

            if (result.Succeeded == false)
            {
                return result;
            }

            return await Store.UpdateAsync(cluster, CancellationToken).ConfigureAwait(false);
        }

        public async Task<List<Cluster>> GetClustersAsync()
        {
            ThrowIfDisposed();

            return await Store.GetClustersAsync(CancellationToken).ConfigureAwait(false);
        }

        public async Task<NantianmenResult> AddDestinationAsync(Cluster cluster, string destinationId)
        {
            ThrowIfDisposed();
            var clusterDestinationStore = GetClusterDestinationStore();
            ArgumentNullException.ThrowIfNull(cluster);
            ArgumentNullException.ThrowIfNullOrWhiteSpace(destinationId);
            if (await clusterDestinationStore.HasDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false))
            {
                return ClusterAlreadyHasDestinationAsync(destinationId);
            }
            await clusterDestinationStore.AddToDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false);
            return await UpdateClusterAsync(cluster).ConfigureAwait(false);
        }

        public async Task<NantianmenResult> AddDestinationsAsync(Cluster cluster, IEnumerable<string> destinationIds)
        {
            ThrowIfDisposed();
            var clusterDestinationStore = GetClusterDestinationStore();
            ArgumentNullException.ThrowIfNull(cluster);
            ArgumentNullException.ThrowIfNull(destinationIds);

            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNull(cluster);
            ArgumentNullException.ThrowIfNull(destinationIds);

            foreach (var destinationId in destinationIds)
            {
                if (await clusterDestinationStore.HasDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false))
                {
                    return ClusterAlreadyHasDestinationAsync(destinationId);
                }

                await clusterDestinationStore.AddToDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false);
            }

            return await UpdateClusterAsync(cluster).ConfigureAwait(false);
        }

        public async Task<NantianmenResult> RemoveDestinationAsync(Cluster cluster, string destinationId)
        {
            ThrowIfDisposed();
            var clusterDestinationStore = GetClusterDestinationStore();
            ArgumentNullException.ThrowIfNull(cluster);

            if (await clusterDestinationStore.HasDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false))
            {
                await clusterDestinationStore.RemoveFromDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false);
                return await UpdateClusterAsync(cluster).ConfigureAwait(false);
            }

            return ClusterNotHasDestinationError(destinationId);
        }

        public async Task<NantianmenResult> RemoveDestinationsAsync(Cluster cluster, IEnumerable<string> destinationIds)
        {
            ThrowIfDisposed();
            var clusterDestinationStore = GetClusterDestinationStore();
            ArgumentNullException.ThrowIfNull(cluster);
            ArgumentNullException.ThrowIfNull(destinationIds);

            foreach (var destinationId in destinationIds)
            {
                if (!await clusterDestinationStore.HasDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false))
                {
                    return ClusterNotHasDestinationError(destinationId);
                }

                await clusterDestinationStore.RemoveFromDestinationAsync(cluster, destinationId, CancellationToken).ConfigureAwait(false);
            }

            return await UpdateClusterAsync(cluster).ConfigureAwait(false);
        }

        private NantianmenResult ClusterNotHasDestinationError(string destinationId)
        {
            return NantianmenResult.Failed(ErrorDescriber.ClusterNotHasDestination(destinationId));
        }

        private NantianmenResult ClusterAlreadyHasDestinationAsync(string destinationId)
        {
            return NantianmenResult.Failed(ErrorDescriber.ClusterAlreadyHasDestination(destinationId));
        }

        private IClusterDestinationStore GetClusterDestinationStore()
        {
            if (Store is IClusterDestinationStore clusterDestinationStore)
            {
                return clusterDestinationStore;
            }

            throw new NotSupportedException(Resource.StoreNotIClusterDestinationStore);
        }

        private Task<NantianmenResult> ValidateClusterAsync(Cluster cluster)
        {
            var errors = new List<NantianmenError>();

            if (string.IsNullOrWhiteSpace(cluster.Id))
            {
                errors.Add(ErrorDescriber.ClusterIdMustNotNullAndNotWhiteSpace());
            }

            if (errors.Count > 0)
            {
                return Task.FromResult(NantianmenResult.Failed(errors));
            }

            return Task.FromResult(NantianmenResult.Success);
        }

        public async Task<Cluster?> FindClusterAsync(string clusterId)
        {
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);

            return await Store.FindClusterByIdAsync(clusterId, CancellationToken);
        }

        public async Task<NantianmenResult> RemoveClusterAsync(string clusterId)
        {
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);

            var cluster = await Store.FindClusterByIdAsync(clusterId, CancellationToken);

            if (cluster is null)
            {
                return NantianmenResult.Failed(ErrorDescriber.ClusterNotFound(clusterId));
            }

            return await Store.RemoveAsync(cluster, CancellationToken);
        }

        public async Task<List<Destination>> ListDestinationsAsync(string clusterId)
        {
            ThrowIfDisposed();
            ArgumentNullException.ThrowIfNullOrWhiteSpace(clusterId);

            var clusterDestinations = await Store.FindClusterDestinationsAsync(clusterId, CancellationToken);

            return clusterDestinations;
        }
    }
}
